package com.codyy.felix.webconsole.plugins.route.core;

import com.codyy.felix.webconsole.plugins.route.core.pojo.RouteParams;
import com.codyy.felix.webconsole.plugins.route.core.pojo.RouteProgress;
import com.codyy.felix.webconsole.plugins.route.core.pojo.RouteResult;
import com.codyy.felix.webconsole.plugins.route.core.utils.RouteXmlInfo;
import com.codyy.felix.webconsole.plugins.route.exception.RouteException;
import org.apache.commons.fileupload.FileItem;
import org.apache.commons.lang3.StringUtils;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
import org.osgi.framework.BundleException;

import java.io.File;
import java.io.IOException;
import java.util.*;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.stream.Collectors;

public class RouteService {


	private static final int PROVIDER_THREAD_POOL_SIZE_DEFAULT = 8;

	private static final int PHRASE_CACHE_SIZE_DEFAULT = 60;

	private static final int RANK_BOOST_DEFAULT = 1000;

	private static final int PROVIDER_TIMEOUT_DEFAULT = 30;

	private static final String LOCATION_PREFIX = "blueprint:file:";

	private static final String BUNDLE_PREFIX = "codyy-route-";

	private ExecutorService executor;

	private Map<Integer, RouteProgress> phraseCache;

	private int rankBoost;

	private int providerTimeout;


	public RouteService(BundleContext bundleContext) {
		this(
				PROVIDER_THREAD_POOL_SIZE_DEFAULT, PHRASE_CACHE_SIZE_DEFAULT, RANK_BOOST_DEFAULT, PROVIDER_TIMEOUT_DEFAULT
		);
	}

	public RouteService(int providerThreadPoolSize, final int phraseCacheSize, int rankBoost, int providerTimeout) {
		this.executor = Executors.newFixedThreadPool(providerThreadPoolSize);
		this.phraseCache = Collections.synchronizedMap(new LinkedHashMap<Integer, RouteProgress>() {
			@Override
			protected boolean removeEldestEntry(Map.Entry<Integer, RouteProgress> entry) {
				return size() > phraseCacheSize;
			}
		});
		this.rankBoost = rankBoost;
		this.providerTimeout = providerTimeout;

		reset();
	}


	public void dispose() {
		this.executor.shutdownNow();
	}

	/**
	 * 查询路由列表
	 */
	public RouteProgress search(final RouteParams params, Bundle[] bundles) {
		RouteProgress progress = new RouteProgress(params);
		progress.start();
		List<RouteResult> routeList = this.bundleToRouteXmlInfo(bundles);
		if (StringUtils.isNotBlank(params.getPhrase())) {
			routeList = routeList.stream()
					.filter(o -> StringUtils.isNotBlank(o.getRouteName()) && o.getRouteName().contains(params.getPhrase()))
					.collect(Collectors.toList());
		}
		if (params.getResultLimit() >= 1) {
			int limit = params.getResultLimit() >= routeList.size() ? routeList.size() : params.getResultLimit();
			routeList = routeList.subList(0, limit);
		}
		progress.getResults().addAll(routeList);
		progress.stop();

		return progress;
	}



	private List<RouteResult> bundleToRouteXmlInfo(Bundle[] bundles) {
		List<RouteResult> routeList = new ArrayList<>();

		for (Bundle bundle : bundles) {
			if (bundle.getSymbolicName().startsWith(BUNDLE_PREFIX)
					&& bundle.getLocation().startsWith("blueprint:file:")
					&& bundle.getLocation().endsWith(".xml")
					&& bundle.getState() != Bundle.UNINSTALLED) {
				RouteXmlInfo routeXmlInfo = new RouteXmlInfo(bundle.getLocation().substring(LOCATION_PREFIX.length()));
				routeXmlInfo.setRouteInfo();
				routeXmlInfo.setRouteNameAndDesc();
				routeList.add(new RouteResult(
						String.valueOf(bundle.getBundleId()),
						bundle.getSymbolicName(),
						routeXmlInfo.getRouteName(),
						routeXmlInfo.getRouteDesc(),
						routeXmlInfo.getFromRoute(),
						StringUtils.join(routeXmlInfo.getDesNames(), ","),
						RouteResult.RouteState.getNameByIndex(bundle.getState())
				));
			}
		}
		return routeList;
	}

	/**
	 * 校验路由文件
	 */
	public String validRouteFile(FileItem fileItem, Bundle[] bundles) throws IOException {
		RouteXmlInfo routeXmlInfo = new RouteXmlInfo(fileItem.getInputStream());
		routeXmlInfo.setRouteInfo();
		String routeFrom = routeXmlInfo.getFromRoute();
		if (StringUtils.isBlank(routeFrom)) {
			return "缺少from标签。";
		}
		List<RouteResult> routeList = this.bundleToRouteXmlInfo(bundles);
		for (RouteResult routeResult : routeList) {
			if (routeResult != null) {
				if (fileItem.getName().equals(routeResult.getRouteFileName())) {
					return "路由文件名重复";
				}
				if (routeFrom.equals(routeResult.getOriginName())) {
					return "存在相同的数据源";
				}

			}
		}
		return "";
	}

	/**
	 * 拷贝路由到deploy目录下
	 */
	public void coptyToDeploy(FileItem fileItem, String deployPath) throws Exception {
		File targetFile = new File(deployPath + File.separator +  fileItem.getName());
		fileItem.write(targetFile);
	}

	/**
	 * 更新路由文件
	 */
	public void updateFile(FileItem fileItem, Bundle bundle) {
		if (bundle == null) {
			throw new RouteException("待替换文件已卸载.");
		}
		if (StringUtils.isBlank(bundle.getSymbolicName()) || !bundle.getSymbolicName().startsWith(RouteService.BUNDLE_PREFIX)) {
			throw new RouteException("待替换文件不是自定义路由文件，不允许替换.");
		}
		String targetFileName = Optional.ofNullable(bundle.getLocation())
				.filter(s -> s.endsWith(".xml"))
				.map(s -> s.substring(LOCATION_PREFIX.length()))
				.orElse("");
		if (StringUtils.isBlank(targetFileName)) {
			throw new RouteException("待替换文件不是规范的路由配置文件.不允许替换");
		}
		File file = new File(targetFileName);
		if (!file.exists()) {
			throw new RouteException("待替换文件不存在.");
		}
		try {
			fileItem.write(file);
		} catch (Exception e) {
			throw new RouteException("写文件失败，请确认是否当前用户是否有权限.");
		}
	}

	/**
	 * 删除路由文件
	 */
	public void deleteRouteFile(Bundle bundle) throws BundleException {
		if (bundle == null) {
			throw new RouteException("当前路由已被管理员卸载,不允许删除.");
		}
		bundle.uninstall();
		String fileName = Optional.ofNullable(bundle.getLocation())
				.map( s -> s.substring(LOCATION_PREFIX.length()))
				.orElse("");
		if (StringUtils.isNotBlank(fileName)) {
			File file = new File(fileName);
			if (file.exists()) {
				boolean b = file.delete();
				if (!b) {
					throw new RouteException("删除失败,请确认运行环境有权限删除文件。");
				}
			} else {
				throw new RouteException("路由文件不存在.");
			}
		} else {
			throw new RouteException("文件名解析失败.");
		}
	}

	public void reset() {
		phraseCache.clear();
	}

}
